#  This file is part of OpenLightingDesigner.
# 
#  OpenLightingDesigner is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  OpenLightingDesigner is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with OpenLightingDesigner.  If not, see <http://www.gnu.org/licenses/>.
#
# controller.py
# Copyright (c) 2010 - 2011 Matthew Reid

import math

from Bases import BaseObject, setID
from Bases.Properties import PropertyConnector
from Bases.misc import Interpolator
from DeviceSystem.Groups.Group import GroupBase

class BaseController(BaseObject, PropertyConnector):
    _saved_attributes = ['id', 'name']
    def __init__(self, **kwargs):
        self.prop_key = kwargs.get('prop_key')
        self.value_is_string = False
        self.value_is_bool = False
        super(BaseController, self).__init__(**kwargs)
        self.prototype = kwargs.get('prototype')
        if 'deserialize' not in kwargs:
            self.id = setID(kwargs.get('id'))
            self.name = kwargs.get('name')
        self.time_offset = kwargs.get('time_offset', 0.)
        self.time_range = kwargs.get('time_range', [0., 1.])
        self.value_wrap_around = kwargs.get('value_wrap_around', False)
        self.Property = kwargs.get('Property')
        
    def unlink(self):
        super(BaseController, self).unlink()
        self.Property = None
    
    def attach_Property(self, prop):
        super(BaseController, self).attach_Property(prop)
        wrap_around = getattr(prop.parent_obj, 'value_wrap_around', False)
        if isinstance(wrap_around, dict):
            newval = wrap_around.get(self.prop_key)
            if newval is not None:
                self.value_wrap_around = newval
                
    def on_interval(self, timeV):
        if self.time_offset != 0.:
            timeV += self.time_offset
            tmin, tmax = self.time_range
            trange = tmax - tmin
            if timeV > tmax:
                timeV -= trange
            elif timeV < tmin:
                timeV += trange
            if timeV > tmax or timeV < tmin:
                self.LOG.warning('timeV out of range because of dumb math: timeV=%s, range=%s, offset=%s' % (timeV, self.time_range, self.time_offset))
        value = self.calclulate_value(timeV)
        if self.value_is_bool and value is False:
            return
        if self.prop_key is not None:
            return value
        self.set_Property_value(value)
    
    def store_value(self, *args, **kwargs):
        pass
    
    def calclulate_value(self, timeV):
        pass
        
    def get_Property_value(self):
        value = super(BaseController, self).get_Property_value()
        if value is None:
            return
        if self.prop_key is not None:
            return value[self.prop_key]
        return value
        
    def set_Property_value(self, value, normalize=False):
        if self.prop_key is not None:
            value = {self.prop_key:value}
            if normalize:
                self.Property.normalized_and_offset = value
            else:
                self.Property.set_value(value)
            #super(BaseController, self).set_Property_value(value)
        else:
            if normalize:
                self.Property.normalized_and_offset = value
            else:
                super(BaseController, self).set_Property_value(value)
        
    def get_prop_normalized_and_offset(self):
        if self.Property is None:
            return False
        value = self.Property.normalized_and_offset
        if self.prop_key is not None:
            return value.get(self.prop_key)
        return value
        
    def set_prop_normalized_and_offset(self, value):
        if self.Property is None:
            return
        if self.prop_key is not None:
            value = {self.prop_key:value}
        self.Property.normalized_and_offset = value
        if self.prop_key in ['pan', 'tilt']:
            prop = self.Property
            self.LOG.debug('%s min=%s, max=%s, norm=%s, value=%s' % (self.prop_key, prop.min, prop.max, value, self.Property.value))
        
    def calculate_prop_normalized_and_offset(self, value):
        if self.Property is None:
            return value
        min = self.Property.min
        max = self.Property.max
        if self.prop_key is not None:
            min = min[self.prop_key]
            max = max[self.prop_key]
        return value / (max - min) - min
        
class KeyframeController(BaseController):
    _saved_class_name = 'KeyframeController'
    _saved_attributes = ['hook_style', 'keyframes']
    _Properties = {'keyframes':dict(default={})}
    def __init__(self, **kwargs):
        super(KeyframeController, self).__init__(**kwargs)
        self.interpolator = Interpolator()
        if 'deserialize' not in kwargs:
            self.hook_style = kwargs.get('hook_style', 'linear')
            self.keyframes = {}
            keyframes = kwargs.get('keyframes', {})
            self.add_keyframes(keyframes)
        self.update_keyframe_array()
        
    def on_property_changed(self, **kwargs):
        prop = kwargs.get('Property')
        value = kwargs.get('value')
        if prop.name == 'keyframes':
            self.add_keyframes(value)
            
    def store_value(self, timeV, value=None):
        if value is not None:
            self.add_keyframes({timeV:value})
        else:
            self.store_current_value(timeV)
        
    def store_current_value(self, timeV):
        value = self.get_Property_value()
        self.add_keyframes({timeV:value})
        
    def add_keyframes(self, d):
        d = d.copy()
        self.keyframes.update(d)
        self.update_keyframe_array()
        
    def update_keyframe_array(self):
        for v in self.keyframes.itervalues():
            if type(v) == str or v is None:
                self.value_is_string = True
                return
            elif type(v) == bool:
                self.value_is_bool = True
                return
        keys = self.keyframes.keys()
        keys.sort()
        l1 = [float(key) for key in keys]
        l2 = [float(self.keyframes.get(key)) for key in keys]
        if self.value_wrap_around:
            for i, val in enumerate(l2):
                next = i + 1
                if i == len(l2) - 1:
                    break
                    next = 0
                if val > l2[next]:
                    if val - l2[next] > .5:
                        l2[next] += 1.
                else:
                    if l2[next] - val > .5:
                        l2[i] += 1.
            #print 'wrap around: old=%s, new=%s' % ([float(self.keyframes.get(key)) for key in keys], l2)
        for x, y in zip(l1, l2):
            self.interpolator.add_point(x, y)        
        flag = True
        if len(l2) == 0 or l2.count(l2[0]) == len(l2):
            flag = False
        self.does_change = flag
        
    def calclulate_value(self, timeV):
        if self.value_is_bool:
            return self.keyframes.get(timeV)
        if self.value_is_string:
            #timeV = round(timeV)
            value = self.keyframes.get(timeV)
            #print self.id, timeV, value
            if value is None:
                if len(self.keyframes) == 2:
                    last = max(self.keyframes.keys())
                    value = self.keyframes[last]
                    if timeV >= last and self.get_Property_value() != value:
                        return value
                    return False
                
                elif value == self.get_Property_value():
                    return False
            
            return value
        val = self.interpolator.solve_y(timeV)
        if self.value_wrap_around and val > 1.:
            #print self.src_object, 'wrapping val: ', val
            val -= 1.
        return val
        
class FunctionalController(BaseController):
    _saved_class_name = 'FunctionalController'
    _saved_attributes = ['expression']
    _Properties = {'expression':dict(default='', fformat='_expression_format')}
    def __init__(self, **kwargs):
        super(FunctionalController, self).__init__(**kwargs)
        if 'deserialize' not in kwargs:
            self.expression = kwargs.get('expression', 'value=timeV')
        self.function = kwargs.get('function', self._default_function)
    
    def _expression_format(self, value):
        if '=' in value:
            value = value.split('=')[1]
        return value
        
    def on_property_changed(self, **kwargs):
        prop = kwargs.get('Property')
        value = kwargs.get('value')
        if prop.name == 'expression':
            self.expression = value
            
    def set_Property_value(self, value):
        super(FunctionalController, self).set_Property_value(value, True)
        
    def _default_function(self, timeV):
        #x = timeV
        return eval(self.expression)
        
    def calclulate_value(self, timeV):
        value = self.function(timeV)
        return value

class MultiController(BaseController):
    _saved_attributes = ['controller_type']
    _saved_child_objects = ['controllers']
    _saved_child_classes = [KeyframeController, FunctionalController]
    def __init__(self, **kwargs):
        self._expression = {}
        self._time_offset = {}
        self.init_kwargs = kwargs.copy()
        if 'deserialize' not in kwargs:
            self.controllers = {}
            self.controller_type = kwargs.get('controller_type')
        super(MultiController, self).__init__(**kwargs)
        
    def unlink(self):
        for key in self.controllers.keys()[:]:
            self.controllers[key].unlink()
            del self.controllers[key]
        super(MultiController, self).unlink()
    
    def attach_Property(self, prop):
        super(MultiController, self).attach_Property(prop)
        cls = controllerTypes.get(self.controller_type)
        if isinstance(prop.value, dict):
            for key in prop.value.iterkeys():
                c_kwargs = self.init_kwargs.copy()
                c_kwargs.update({'prop_key':key, 'id':key, 'parent_controller':self})
                c = cls(**c_kwargs)
                if self.controller_type == 'Functional':
                    c.bind(expression=self._on_child_expression_set)
                self.controllers.update({c.id:c})
                
    def unlink_Property(self, prop):
        super(MultiController, self).unlink_Property(prop)
        for c in self.controllers.itervalues():
            c.unlink_Property(prop)
            
    @property
    def time_offset(self):
        return self._time_offset
    @time_offset.setter
    def time_offset(self, value):
        if isinstance(value, dict):
            self._time_offset.update(value)
            for key, val in value.iteritems():
                c = self.controllers.get(key)
                if c:
                    c.time_offset = val
        else:
            for key, c in self.controllers.iteritems():
                self._time_offset[key] = value
                c.time_offset = value
                
    @property
    def expression(self):
        return self._expression
    @expression.setter
    def expression(self, value):
        self._expression.update(value)
        for key, val in value.iteritems():
            c = self.controllers.get(key)
            if c:
                c.expression = val
            
    def _on_child_expression_set(self, **kwargs):
        value = kwargs.get('value')
        child = kwargs.get('obj')
        key = child.prop_key
        if self._expression.get(key) != value:
            self._expression[key] = value
            
    def store_value(self, timeV, value=None):
        if value is None:
            value = {}
        if not isinstance(timeV, dict):
            timeV = dict(zip(self.controllers.keys(), [timeV]*len(self.controllers)))
        for key, c in self.controllers.iteritems():
            c.store_value(timeV[key], value.get(c.id))
            
    def on_interval(self, timeV):
        prop = self.Property
        if prop is None:
            return
        d = {}
        for key, c in self.controllers.iteritems():
            value = c.on_interval(timeV)
            if value is None:
                continue
            d[key] = value
        if self.controller_type == 'Functional':
            prop.normalized_and_offset = d
        else:
            prop.set_value(d)
            
    def on_property_changed(self, **kwargs):
        for c in self.controllers.itervalues():
            c.on_property_changed(**kwargs)

controllerTypes = {'Keyframe':KeyframeController, 'Functional':FunctionalController, 'Multi':MultiController}
